Skip to main content

Blocking vs Non-Blocking Resources - Complete Guide

Table of Contentsโ€‹

  1. What Does "Blocking" Mean?
  2. The Critical Rendering Path
  3. Blocking Resources
  4. Non-Blocking Resources
  5. Resource Loading Matrix
  6. How Browsers Decide Priority
  7. Optimization Strategies
  8. Visual Flow Diagrams
  9. Real Interview Scenarios
  10. Quick Reference

What Does "Blocking" Mean?โ€‹

A blocking resource is one that prevents the browser from continuing a critical step in the rendering process.

What Can Be Blocked?โ€‹

  1. HTML parsing - Browser stops reading HTML
  2. DOM construction - Building the DOM tree pauses
  3. CSSOM construction - Must wait for CSS to parse
  4. Page rendering - Cannot paint pixels until ready

Non-Blocking Resourcesโ€‹

Non-blocking resources load in parallel without stopping these critical steps. The browser continues parsing and rendering while these resources download.

Why This Mattersโ€‹

Blocking resources directly impact Core Web Vitals:

  • FCP (First Contentful Paint) - When first content appears
  • LCP (Largest Contentful Paint) - When main content appears
  • TTI (Time to Interactive) - When page becomes interactive

The Critical Rendering Pathโ€‹

The browser follows this sequence to render a page:

1. HTML โ†’ DOM (Document Object Model)
2. CSS โ†’ CSSOM (CSS Object Model)
3. DOM + CSSOM โ†’ Render Tree
4. Render Tree โ†’ Layout (calculate positions)
5. Layout โ†’ Paint (draw pixels)

Key Principleโ€‹

Any resource that delays DOM or CSSOM construction blocks rendering.

Example Flowโ€‹

User requests page
โ†“
Download HTML (200ms)
โ†“
Parse HTML โ†’ Build DOM
โ†“ (finds CSS)
Download CSS (300ms) โ† BLOCKS rendering
โ†“
Parse CSS โ†’ Build CSSOM
โ†“
Combine DOM + CSSOM โ†’ Render Tree
โ†“
Layout + Paint
โ†“
First Contentful Paint โœ…

Blocking Resourcesโ€‹

A. CSS (Render-Blocking)โ€‹

CSS is render-blocking - the browser cannot paint pixels until CSSOM is built.

Example:

<link rel="stylesheet" href="styles.css">

Why Blocking?โ€‹

  1. Layout depends on CSS - Need to know sizes, positions, colors
  2. Prevents FOUC - Flash of Unstyled Content
  3. CSSOM must be complete - Can't render partial styles

Blocking Behaviorโ€‹

Blocks HTML Parsing?Blocks Rendering?
โŒ Noโœ… Yes

Note: CSS does NOT block HTML parsing - the browser continues building the DOM. But it DOES block rendering.

Example Timelineโ€‹

0ms:   Start HTML parse
100ms: Discover <link rel="stylesheet">
100ms: Start CSS download (HTML parsing continues)
300ms: CSS downloaded
350ms: CSSOM built
350ms: Render Tree created โ† CSS was blocking this
400ms: First Paint

Media Queries Exceptionโ€‹

CSS with non-matching media queries is NOT render-blocking:

<!-- Blocks rendering on all devices -->
<link rel="stylesheet" href="styles.css">

<!-- Only blocks on print -->
<link rel="stylesheet" href="print.css" media="print">

<!-- Only blocks on mobile -->
<link rel="stylesheet" href="mobile.css" media="(max-width: 640px)">

B. Synchronous JavaScript (Parse-Blocking & Render-Blocking)โ€‹

Synchronous JavaScript blocks both HTML parsing and rendering.

Example:

<script src="app.js"></script>

Why Blocking?โ€‹

  1. JavaScript can modify DOM - document.write(), element creation
  2. JavaScript can modify CSSOM - Change styles dynamically
  3. Execution order matters - Scripts must run in sequence
  4. CSSOM dependency - Script execution waits for CSS to finish

Blocking Behaviorโ€‹

Blocks HTML Parsing?Blocks Rendering?
โœ… Yesโœ… Yes

Example Timelineโ€‹

0ms:   Start HTML parse
100ms: Discover <script src="app.js">
100ms: STOP HTML parsing โ† BLOCKED
100ms: Download app.js
400ms: Download complete
400ms: Execute JavaScript
500ms: Resume HTML parsing โ† UNBLOCKED

JavaScript Waits for CSSโ€‹

Even worse - JavaScript execution waits for pending CSS downloads:

<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>

Timeline:

0ms:   Parse HTML
50ms: Discover CSS, start download
100ms: Discover script
100ms: Script download starts (but execution waits)
300ms: CSS finishes (CSSOM ready)
300ms: Script finishes download
300ms: NOW script can execute โ† Waited for CSS!

Why? Script might query computed styles, so browser ensures CSSOM is ready first.


C. Fonts (Indirectly Blocking)โ€‹

Fonts don't block parsing or initial rendering, but they delay text rendering, causing FOIT (Flash of Invisible Text).

Example:

@font-face {
font-family: 'Inter';
src: url('Inter.woff2') format('woff2');
}

body {
font-family: Inter, sans-serif;
}

Problem: FOITโ€‹

0ms:   Page renders, but text is invisible
0ms: Font downloading...
500ms: Font loaded
500ms: Text suddenly appears โ† Bad UX

Problem: FOUTโ€‹

With font-display: swap:

0ms:   Page renders with fallback font
0ms: Font downloading...
500ms: Font loaded
500ms: Text layout shifts โ† CLS issue

Blocking Behaviorโ€‹

Blocks HTML Parsing?Blocks Rendering?Blocks Text?
โŒ NoโŒ Noโš ๏ธ Yes (3s timeout)

Mitigation Strategiesโ€‹

1. Preload Critical Fonts

<link rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin>

2. Use font-display

@font-face {
font-family: 'Inter';
src: url('Inter.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
}

3. Subset Fonts

Full font:  150 KB
Subset: 30 KB (only needed characters)

Non-Blocking Resourcesโ€‹

A. Imagesโ€‹

Images load asynchronously and do NOT block parsing or rendering.

Example:

<img src="image.jpg" alt="Description">

Non-Blocking Behaviorโ€‹

Blocks HTML Parsing?Blocks Rendering?
โŒ NoโŒ No

How It Worksโ€‹

0ms:   Parse HTML
50ms: Discover <img>
50ms: Continue parsing (doesn't wait)
50ms: Start image download in background
200ms: HTML parsing complete
250ms: First paint (without image)
400ms: Image loads, reflow/repaint

Exception: LCP Imageโ€‹

If the image is the Largest Contentful Paint element, late loading hurts performance.

Problem:

<!-- Hero image discovered late -->
<img src="/hero.jpg" alt="Hero">

Solution:

<!-- Preload + high priority -->
<link rel="preload" href="/hero.jpg" as="image">
<img src="/hero.jpg"
alt="Hero"
fetchpriority="high"
loading="eager">

B. Async JavaScriptโ€‹

Async JavaScript downloads in parallel and executes as soon as ready, without blocking HTML parsing.

Example:

<script src="app.js" async></script>

Async Behaviorโ€‹

Blocks HTML Parsing?Blocks Rendering?Execution Order?
โŒ No (during download)โŒ No (during download)โš ๏ธ Not guaranteed
โš ๏ธ Yes (during execution)โš ๏ธ Yes (during execution)-

Timelineโ€‹

0ms:   Parse HTML
50ms: Discover <script async>
50ms: Start download (parsing continues)
100ms: Still parsing HTML
200ms: Script finishes download
200ms: PAUSE parsing, execute script โ† Brief block
250ms: Resume parsing

When to Use Asyncโ€‹

โœ… Good for:

  • Analytics scripts (Google Analytics)
  • Ad networks
  • Social media widgets
  • Any independent script that doesn't depend on DOM

โŒ Bad for:

  • Scripts that manipulate DOM
  • Scripts with dependencies
  • Scripts that must run in order

Example:

<!-- โœ… Good: Independent -->
<script src="https://www.google-analytics.com/analytics.js" async></script>

<!-- โŒ Bad: Order matters -->
<script src="jquery.js" async></script>
<script src="app.js" async></script> <!-- Might run before jQuery! -->

Defer JavaScript downloads in parallel but waits to execute until HTML parsing is complete.

Example:

<script src="app.js" defer></script>

Defer Behaviorโ€‹

Blocks HTML Parsing?Blocks Rendering?Execution Order?
โŒ NoโŒ Noโœ… Guaranteed

Timelineโ€‹

0ms:   Parse HTML
50ms: Discover <script defer>
50ms: Start download (parsing continues)
100ms: Script downloaded (keeps parsing)
200ms: HTML parsing complete
200ms: DOMContentLoaded about to fire
200ms: Execute all deferred scripts (in order)
250ms: DOMContentLoaded fires

Why Defer is Bestโ€‹

  1. Non-blocking - Downloads in parallel
  2. Predictable - Executes in order
  3. DOM ready - Full DOM available when script runs
  4. Performance - Doesn't delay FCP

When to Use Deferโ€‹

โœ… Use defer for:

  • Application JavaScript
  • UI frameworks (React, Vue)
  • DOM manipulation scripts
  • Any script that needs the full DOM

Example:

<!-- All defer, execute in order -->
<script src="utils.js" defer></script>
<script src="components.js" defer></script>
<script src="app.js" defer></script>

D. Lazy-Loaded Resourcesโ€‹

Resources loaded only when needed (typically when scrolled into view).

Example:

<img src="image.jpg" loading="lazy" alt="Below fold">

Lazy Loading Behaviorโ€‹

Blocks HTML Parsing?Blocks Rendering?When Loaded?
โŒ NoโŒ NoWhen near viewport

Benefitsโ€‹

  1. Saves bandwidth - Only loads visible content
  2. Faster initial load - Fewer requests
  3. Better performance - Prioritizes above-fold content

Example Usageโ€‹

<!-- Above-the-fold: Load immediately -->
<img src="hero.jpg" loading="eager" fetchpriority="high">

<!-- Below-the-fold: Lazy load -->
<img src="gallery-1.jpg" loading="lazy">
<img src="gallery-2.jpg" loading="lazy">
<img src="gallery-3.jpg" loading="lazy">

Native vs JavaScriptโ€‹

<!-- Native (preferred) -->
<img src="image.jpg" loading="lazy">

<!-- JavaScript (for older browsers) -->
<img data-src="image.jpg" class="lazy">
<script>
// Intersection Observer implementation
</script>

Resource Loading Matrixโ€‹

Quick reference table showing what each resource type blocks.

ResourceBlocks Parsing?Blocks Rendering?Notes
HTMLN/AN/ASequential parsing
CSSโŒ Noโœ… YesRender-blocking
JS (sync)โœ… Yesโœ… YesParse & render blocking
JS (async)โŒ No*โŒ No**Execution can block briefly
JS (defer)โŒ NoโŒ NoExecutes after parsing
ImagesโŒ NoโŒ NoAlways non-blocking
FontsโŒ Noโš ๏ธ IndirectFOIT - text waits for font
VideosโŒ NoโŒ NoAlways non-blocking
IframesโŒ NoโŒ NoIndependent document

Visual Priorityโ€‹

Highest Priority (Blocking):
โ”œโ”€ HTML parsing
โ”œโ”€ CSS (CSSOM construction)
โ””โ”€ Synchronous JavaScript

Medium Priority (Non-blocking but important):
โ”œโ”€ Deferred JavaScript
โ”œโ”€ Async JavaScript
โ””โ”€ Preloaded resources

Low Priority (Opportunistic):
โ”œโ”€ Images (below fold)
โ”œโ”€ Lazy-loaded content
โ””โ”€ Prefetched resources

How Browsers Decide Priorityโ€‹

Understanding browser prioritization helps optimize loading strategies.

1. HTML is Parsed Sequentiallyโ€‹

<html>
<head>
<!-- Parsed first -->
</head>
<body>
<!-- Parsed second -->
</body>
</html>

Browser discovers resources in document order and assigns priority based on:

  • Resource type
  • Location in document
  • Attributes (async/defer)
  • Media queries

2. CSSOM Must Be Complete Before Paintingโ€‹

Cannot paint without CSSOM because:
โ”œโ”€ Layout calculations need dimensions
โ”œโ”€ Visual styles need colors/fonts
โ””โ”€ Positioning needs CSS rules

This is why CSS is render-blocking - it's not optional for rendering.


3. JavaScript Can Block Because It Mutates DOM/CSSOMโ€‹

JavaScript has the power to:

// Modify DOM
document.write('<div>New content</div>');
document.getElementById('header').remove();

// Modify CSSOM
element.style.color = 'red';
document.body.classList.add('dark-mode');

// Query computed styles (requires CSSOM)
const width = element.offsetWidth;
const color = getComputedStyle(element).color;

Because of this power, browsers must:

  1. Stop parsing when encountering <script>
  2. Wait for CSSOM if script might query styles
  3. Execute script before continuing

4. Images Are Independent from Layout (Mostly)โ€‹

Images don't affect:

  • HTML structure (DOM)
  • CSS rules (CSSOM)
  • JavaScript execution
  • Other resource loading

Exception: Image dimensions can cause reflow if not specified.

Good practice:

<!-- Prevents layout shift -->
<img src="photo.jpg" width="800" height="600" alt="Photo">

Browser Priority Levelsโ€‹

Browsers assign priority levels to resources:

PriorityResource Types
HighestHTML document
HighCSS (render-blocking)
HighFonts (in use)
HighSync scripts (in <head>)
HighPreloaded resources
MediumScripts (defer/async)
MediumImages (above fold)
LowImages (below fold)
LowestPrefetched resources

You can override these with fetchpriority:

<img src="hero.jpg" fetchpriority="high">
<script src="analytics.js" fetchpriority="low"></script>

Optimization Strategiesโ€‹

CSS Optimizationsโ€‹

1. Inline Critical CSSโ€‹

<head>
<!-- Inline critical above-the-fold CSS -->
<style>
.hero { display: flex; height: 100vh; }
.nav { position: fixed; top: 0; }
</style>

<!-- Load full CSS asynchronously -->
<link rel="preload" href="/styles.css" as="style"
onload="this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/styles.css">
</noscript>
</head>

Impact: Reduces render-blocking time from 300ms to 0ms.


2. Split CSS by Media Queryโ€‹

<!-- Only blocks on matching media -->
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)">
<link rel="stylesheet" href="desktop.css" media="(min-width: 769px)">
<link rel="stylesheet" href="print.css" media="print">

3. Minify and Compressโ€‹

# Before
styles.css: 150 KB

# After minification
styles.min.css: 120 KB (-20%)

# After gzip
styles.min.css.gz: 25 KB (-83% total)

JavaScript Optimizationsโ€‹

1. Use Defer by Defaultโ€‹

<!-- โŒ Bad: Blocks parsing -->
<script src="/app.js"></script>

<!-- โœ… Good: Non-blocking -->
<script src="/app.js" defer></script>

2. Code Splittingโ€‹

// Before: One large bundle
app.js: 500 KB

// After: Split by route
main.js: 50 KB (critical)
home.chunk.js: 80 KB (lazy)
about.chunk.js: 60 KB (lazy)
products.chunk.js: 120 KB (lazy)

Implementation (Webpack/Vite):

// Dynamic import
const AboutPage = () => import('./pages/About');

// React.lazy
const About = React.lazy(() => import('./pages/About'));

3. Tree Shakingโ€‹

Remove unused code:

// Before
import _ from 'lodash'; // 70 KB

// After
import debounce from 'lodash/debounce'; // 2 KB

4. Avoid Long Main-Thread Tasksโ€‹

// โŒ Bad: Long blocking task
function processItems(items) {
items.forEach(item => {
// Complex calculations
heavyOperation(item);
});
} // Blocks for 2 seconds!

// โœ… Good: Break into chunks
async function processItems(items) {
for (let i = 0; i < items.length; i++) {
heavyOperation(items[i]);

// Yield to browser every 50ms
if (i % 10 === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
}

Font Optimizationsโ€‹

1. Preload + font-displayโ€‹

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin>

<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
}
</style>

2. Font Subsettingโ€‹

# Full font
Inter-Regular.woff2: 150 KB

# Subset (Latin only)
Inter-Regular-Latin.woff2: 30 KB (-80%)

Tools:

  • glyphhanger
  • subfont
  • Font Squirrel

Image Optimizationsโ€‹

1. Prioritize LCP Imageโ€‹

<link rel="preload" href="/hero.webp" as="image">
<img src="/hero.webp"
fetchpriority="high"
loading="eager"
width="1920"
height="1080">

2. Lazy Load Below-Foldโ€‹

<img src="/gallery-1.jpg" loading="lazy" width="400" height="300">
<img src="/gallery-2.jpg" loading="lazy" width="400" height="300">

3. Use Modern Formatsโ€‹

<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="Hero">
</picture>

File size comparison:

JPEG: 100 KB
WebP: 60 KB (-40%)
AVIF: 40 KB (-60%)

Visual Flow Diagramsโ€‹

Synchronous Script Blockingโ€‹


Async Script (Non-Blocking)โ€‹


Defer Script (Best Practice)โ€‹


Resource Priority Timelineโ€‹


Real Interview Scenariosโ€‹

โ“ Why is CSS render-blocking but not parse-blocking?โ€‹

Answer:

CSS is render-blocking because the browser needs CSSOM to calculate layout and paint pixels. However, it doesn't block HTML parsing because:

  1. Browser can parse HTML independently - Building DOM doesn't require style information
  2. Progressive rendering optimization - Browser continues discovering resources while CSS downloads
  3. Parallel operations - DOM construction and CSSOM construction happen in parallel

Example:

<html>
<head>
<link rel="stylesheet" href="styles.css"> <!-- Downloads while parsing continues -->
</head>
<body>
<div>Content</div> <!-- Parsed immediately -->
<img src="photo.jpg"> <!-- Discovered and queued -->
</body>
</html>

Timeline:

0ms:   Start parsing HTML
50ms: Discover CSS, start download (parsing continues)
100ms: Parse <div>, <img> tags
200ms: HTML parsing complete (DOM ready)
300ms: CSS download complete
350ms: CSSOM built
350ms: First render โ† CSS was blocking this

โ“ Why does async JavaScript still sometimes hurt performance?โ€‹

Answer:

While async doesn't block during download, execution can still hurt performance because:

  1. Execution blocks main thread - When script executes, it can delay rendering
  2. Unpredictable timing - Might execute during critical rendering phase
  3. Parser pause - Execution interrupts HTML parsing if still in progress
  4. No guarantee on readiness - Might execute before DOM elements it needs

Example of bad timing:

<html>
<body>
<div id="hero">...</div>
<script src="heavy.js" async></script> <!-- Executes whenever ready -->
<!-- More content... -->
</body>
</html>

If heavy.js finishes downloading while browser is trying to render, it blocks rendering.

Better approach:

<script src="heavy.js" defer></script> <!-- Waits until HTML complete -->

Or:

<script src="analytics.js" async></script> <!-- OK for analytics -->

โ“ What is the safest script loading strategy for modern web apps?โ€‹

Answer:

defer + code splitting is the safest default strategy:

<!-- Critical scripts with defer -->
<script src="/main.js" defer></script>
<script src="/components.js" defer></script>

<!-- Analytics with async (independent) -->
<script src="https://analytics.com/script.js" async></script>

Why defer is safest:

  1. โœ… Non-blocking - Downloads in parallel with HTML parsing
  2. โœ… Predictable order - Executes in document order
  3. โœ… DOM ready - Full DOM available when script runs
  4. โœ… After CSSOM - Styles are available for queries
  5. โœ… Before DOMContentLoaded - Runs before event fires

When to use async:

Only for truly independent scripts:

  • Analytics (Google Analytics, Mixpanel)
  • Ad networks
  • Social widgets (Facebook, Twitter)
  • Error tracking (Sentry)

Modern pattern:

<head>
<!-- Framework -->
<script src="/react.js" defer></script>
<script src="/react-dom.js" defer></script>

<!-- App code -->
<script src="/app.js" defer></script>

<!-- Independent scripts -->
<script src="https://www.google-analytics.com/analytics.js" async></script>
</head>

With code splitting:

// main.js
const routes = {
home: () => import('./pages/Home'),
about: () => import('./pages/About'),
contact: () => import('./pages/Contact')
};

// Load route dynamically
const page = await routes[currentRoute]();

โ“ How do you debug render-blocking resources?โ€‹

Answer:

Use these tools and techniques:

1. Chrome DevTools Network Panel:

Filter by:
- Priority column: Look for "Highest" and "High"
- Waterfall: Find resources that delay first paint
- Size: (from cache) vs (from disk cache)

2. Coverage Tool:

Chrome DevTools โ†’ More tools โ†’ Coverage
Shows unused CSS/JS that's blocking unnecessarily

3. Lighthouse Audit:

"Eliminate render-blocking resources"
Lists specific files to optimize

4. WebPageTest:

Shows visual timeline
Highlights blocking resources clearly

Example findings:

โŒ Problem: styles.css (150 KB, blocks 800ms)
โœ… Solution: Inline critical CSS, defer rest

โŒ Problem: jquery.js (90 KB, blocks 300ms)
โœ… Solution: Remove jQuery, use native JS

โŒ Problem: fonts block text for 2s
โœ… Solution: font-display: swap + preload

โ“ Can images ever be render-blocking?โ€‹

Answer:

Images themselves are NOT render-blocking, but they can affect LCP (Largest Contentful Paint) indirectly:

Scenario 1: LCP Element is an Image

<!-- Hero image discovered late -->
<img src="/hero.jpg" alt="Hero">

Problem: Browser doesn't know it's important until it parses the <img> tag.

Solution:

<!-- Hint browser to load early -->
<link rel="preload" href="/hero.jpg" as="image">
<img src="/hero.jpg" fetchpriority="high" loading="eager">

Scenario 2: Image Loaded via CSS

.hero {
background-image: url('/hero.jpg'); /* Discovered late! */
}

Problem: Browser must download CSS, parse it, then discover image.

Solution:

<!-- Preload background image -->
<link rel="preload" href="/hero.jpg" as="image">

Scenario 3: Missing Dimensions

<!-- Causes layout shift (CLS) -->
<img src="/photo.jpg" alt="Photo">

<!-- Prevents reflow -->
<img src="/photo.jpg" width="800" height="600" alt="Photo">

While not technically "blocking", layout shifts hurt user experience.


Quick Referenceโ€‹

Resource Blocking Quick Matrixโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Resource โ”‚ Blocks Parse? โ”‚ Blocks Render? โ”‚ Solution โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ CSS โ”‚ โŒ โ”‚ โœ… โ”‚ Inline crit. โ”‚
โ”‚ JS (sync) โ”‚ โœ… โ”‚ โœ… โ”‚ Use defer โ”‚
โ”‚ JS (async) โ”‚ โŒ โ”‚ โŒ โ”‚ For analyticsโ”‚
โ”‚ JS (defer) โ”‚ โŒ โ”‚ โŒ โ”‚ Default โœ… โ”‚
โ”‚ Images โ”‚ โŒ โ”‚ โŒ โ”‚ Optimize LCP โ”‚
โ”‚ Fonts โ”‚ โŒ โ”‚ โš ๏ธ โ”‚ font-display โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Script Loading Decision Treeโ€‹

Need to load JavaScript?
โ”œโ”€ Does it manipulate DOM?
โ”‚ โ”œโ”€ Yes โ†’ Use defer
โ”‚ โ””โ”€ No โ†’ Is it independent (analytics)?
โ”‚ โ”œโ”€ Yes โ†’ Use async
โ”‚ โ””โ”€ No โ†’ Use defer
โ”‚
โ””โ”€ Can it load later (below-fold)?
โ”œโ”€ Yes โ†’ Dynamic import / lazy load
โ””โ”€ No โ†’ Use defer

CSS Loading Decision Treeโ€‹

Need to load CSS?
โ”œโ”€ Above-the-fold styles?
โ”‚ โ”œโ”€ Yes โ†’ Inline in <style> tag
โ”‚ โ””โ”€ No โ†’ External stylesheet
โ”‚
โ”œโ”€ Large stylesheet?
โ”‚ โ”œโ”€ Yes โ†’ Split into critical + non-critical
โ”‚ โ””โ”€ No โ†’ Single external file
โ”‚
โ””โ”€ Media-specific?
โ”œโ”€ Yes โ†’ Use media queries
โ””โ”€ No โ†’ Load normally

Optimization Priority Orderโ€‹

High Impact, Low Effort:

  1. โœ… Add defer to all app scripts
  2. โœ… Inline critical CSS (< 14 KB)
  3. โœ… Preload LCP image
  4. โœ… Add font-display: swap

Medium Impact, Medium Effort: 5. โœ… Code split JavaScript 6. โœ… Minify CSS/JS 7. โœ… Enable compression (Gzip/Brotli) 8. โœ… Lazy load below-fold images

High Impact, High Effort: 9. โœ… Remove unused CSS/JS 10. โœ… Implement service worker 11. โœ… Switch to HTTP/2 12. โœ… Optimize third-party scripts


Blocking Time Estimatesโ€‹

Typical blocking times by resource type:

ResourceAvg Blocking TimeImpact Level
External CSS (50 KB)200-400ms๐Ÿ”ด High
Sync JS (100 KB)300-600ms๐Ÿ”ด High
Web font (30 KB)100-300ms๐ŸŸก Medium
Render-blocking above500-1000ms combined๐Ÿ”ด Critical

Target: Keep total blocking time under 600ms.


Common Blocking Patterns to Avoidโ€‹

โŒ Pattern 1: Multiple Blocking Scriptsโ€‹

<!-- BAD: 3 blocking scripts = 900ms+ -->
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
<script src="app.js"></script>

Fix:

<!-- GOOD: All deferred = 0ms blocking -->
<script src="jquery.js" defer></script>
<script src="bootstrap.js" defer></script>
<script src="app.js" defer></script>

โŒ Pattern 2: CSS Before Scriptsโ€‹

<!-- BAD: Script waits for CSS -->
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>

Timeline:

0-300ms: CSS downloads (script waits)
300-400ms: CSS parses
400-700ms: Script downloads
700-800ms: Script executes
Total: 800ms blocked

Fix:

<!-- GOOD: Script loads independently -->
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>

โŒ Pattern 3: Multiple CSS Filesโ€‹

<!-- BAD: Multiple roundtrips -->
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="layout.css">
<link rel="stylesheet" href="components.css">
<link rel="stylesheet" href="theme.css">

Fix:

<!-- GOOD: Single bundled file -->
<link rel="stylesheet" href="styles.min.css">

<!-- OR: Critical inline + rest async -->
<style>/* Critical CSS */</style>
<link rel="preload" href="styles.css" as="style"
onload="this.rel='stylesheet'">

โŒ Pattern 4: Late Font Discoveryโ€‹

<link rel="stylesheet" href="styles.css">
<!-- Font discovered only after CSS downloads -->

Fix:

<!-- GOOD: Preload font -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin>
<link rel="stylesheet" href="styles.css">

Performance Checklistโ€‹

Before deploying:

Critical Resourcesโ€‹

  • No more than 2-3 render-blocking CSS files
  • All JavaScript uses defer or async
  • Critical CSS inlined (< 14 KB)
  • LCP image preloaded

JavaScriptโ€‹

  • No synchronous scripts in <head>
  • Third-party scripts use async
  • Bundle size < 200 KB (gzipped)
  • Code splitting implemented

CSSโ€‹

  • Single bundled CSS file (or critical inline)
  • Unused CSS removed
  • Minified and compressed
  • Media queries used for print/mobile-specific styles

Fontsโ€‹

  • Critical fonts preloaded
  • font-display: swap set
  • Fonts subset (if possible)
  • Preconnect to font CDN

Imagesโ€‹

  • LCP image has fetchpriority="high"
  • Below-fold images lazy loaded
  • Dimensions specified (width/height)
  • Modern formats used (WebP/AVIF)

Testingโ€‹

  • Lighthouse score > 90
  • LCP < 2.5s
  • FCP < 1.8s
  • No blocking warnings in DevTools

Real-World Performance Gainsโ€‹

Case Study 1: E-commerce Product Pageโ€‹

Before:

Render-blocking resources:
- styles.css (150 KB) - 400ms
- jquery.js (90 KB) - 300ms
- app.js (200 KB) - 500ms
Total blocking: 1,200ms
LCP: 3.8s

After:

<style>/* Critical CSS - 8 KB */</style>
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
<script src="app.js" defer></script>
<!-- Removed jQuery, used native JS -->

Results:

Total blocking: 0ms (critical CSS inline)
LCP: 1.4s (63% improvement)
Lighthouse: 67 โ†’ 94

Case Study 2: News Websiteโ€‹

Before:

Render-blocking:
- fonts (4 weights) - 600ms
- analytics.js - 200ms
- ads.js - 300ms
Total: 1,100ms
FCP: 2.9s

After:

<link rel="preload" href="/font-regular.woff2" as="font" crossorigin>
<style>
@font-face {
font-family: 'Inter';
font-display: swap;
}
</style>
<script src="analytics.js" async fetchpriority="low"></script>
<script src="ads.js" async fetchpriority="low"></script>

Results:

Total blocking: 150ms (1 font)
FCP: 1.1s (62% improvement)
User engagement: +23%

Case Study 3: SaaS Dashboardโ€‹

Before:

Blocking:
- bootstrap.css (200 KB) - 500ms
- bootstrap.js (80 KB) - 250ms
- app bundle (500 KB) - 800ms
Total: 1,550ms
TTI: 4.5s

After:

<!-- Critical CSS only -->
<style>/* 12 KB critical */</style>

<!-- Code splitting -->
<script src="main.js" defer></script>
<!-- Routes load dynamically -->

<!-- Removed Bootstrap -->
<!-- Used Tailwind (9 KB) -->

Results:

Total blocking: 0ms
Main bundle: 50 KB (90% reduction)
TTI: 1.2s (73% improvement)

Browser Behavior Differencesโ€‹

Different browsers handle blocking slightly differently:

Chrome/Edgeโ€‹

  • Aggressive preload scanner
  • Parallel CSSOM + DOM construction
  • HTTP/2 prioritization

Firefoxโ€‹

  • More conservative preloading
  • Slightly different priority heuristics
  • Good async handling

Safariโ€‹

  • Most conservative preloading
  • Stricter CORS handling for fonts
  • Benefits most from explicit hints

Recommendation: Always test in multiple browsers, but Chrome DevTools gives good baseline metrics.


Advanced Optimization: Critical Request Chainsโ€‹

Critical Request Chain: The sequence of dependent network requests that block rendering.

Example chain:

1. HTML (0ms)
โ”œโ”€ 2. CSS (200ms)
โ”‚ โ””โ”€ 3. Font (400ms)
โ””โ”€ 4. JS (300ms)
โ””โ”€ 5. API call (500ms)

Chain depth: 5 levels Total time: 1,400ms

Optimized chain:

1. HTML (0ms) with inlined critical CSS
โ”œโ”€ 2. Font (preloaded, 200ms parallel)
โ”œโ”€ 3. Full CSS (deferred)
โ””โ”€ 4. JS (defer, 200ms parallel)
โ””โ”€ 5. API (prefetch, parallel)

Chain depth: 2 levels Total time: 200ms

Tools to analyze:

  • Chrome DevTools โ†’ Performance โ†’ Bottom-Up
  • Lighthouse โ†’ Diagnostics โ†’ Critical Request Chains
  • WebPageTest โ†’ Waterfall view

One-Line Interview Summaryโ€‹

"Blocking resources delay parsing or rendering by forcing the browser to wait; minimize them by inlining critical CSS, deferring JavaScript, and using resource hints to parallelize loading on the critical path."


Key Takeawaysโ€‹

  1. CSS blocks rendering but not parsing - necessary for layout
  2. Synchronous JS blocks both - most harmful for performance
  3. defer is the safe default - non-blocking, predictable, DOM-ready
  4. async for independent scripts - analytics, ads, widgets
  5. Images never block - but optimize LCP image priority
  6. Fonts delay text - use font-display: swap + preload
  7. Measure before optimizing - use Lighthouse and DevTools
  8. Inline critical CSS - biggest single performance win

Further Readingโ€‹

Official Documentation:

Tools:

  • Chrome DevTools (Network, Performance, Lighthouse)
  • WebPageTest
  • PageSpeed Insights
  • Critical CSS generators

Related Topics:

  • ๐Ÿ”ฅ Browser Caching Strategies
  • ๐Ÿ”ฅ Browser Hinting Techniques
  • ๐Ÿ”ฅ Critical Rendering Path
  • ๐Ÿ”ฅ Web Vitals Optimization

Last Updated: December 2025